package com.flyxteam.commons.httpclient;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.SSLException;
import javax.servlet.http.Cookie;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

/**
 * HttpClient操作，提供在后台发送HTTP协议的请求
 * @author BZX
 *
 */
public class HttpClientSessionImpl implements HttpClientSession {

	// -------------------- 内部常量 -------------------- //

	/** Http请求方法：post */
	protected static final String METHOD_POST = "post";
	/** Http请求方法：get */
	protected static final String METHOD_GET = "get";
	/** 请求体类型：默认（文本） */
	protected static final String ENCTYPE_DEFAULT = "default";
	/** 请求体类型：多件（二进制、输入流、文件） */
	protected static final String ENCTYPE_MULTIPART = "multipart";

	// -------------------- 内部变量 -------------------- //

	/** HttpClient实例 */
	private CloseableHttpClient httpClient;

	/** HttpClient配置 */
	private HttpClientConfig config;

	// -------------------- 接口实现 -------------------- //

	@Override
	public HttpClientResult doGet(String url, Map<String, Object> params, Map<String, Object> header, HttpClientConfig config, Cookie[] cookies) throws HttpClientTimeoutException {
		return execute(url, METHOD_GET, ENCTYPE_DEFAULT, params, header, config, cookies);
	}
	
	@Override
	public HttpClientResult execute(String url, String method, String enctype, Map<String, Object> params, Map<String, Object> header, HttpClientConfig config, Cookie[] cookies) throws HttpClientTimeoutException {
		HttpClientResult result = new HttpClientResult();

		// 根据方法创建请求的对象
		HttpRequestBase request = createHttpRequest(method);
		// 写入请求体
		setRequestBody(request, url, method, enctype, params, config);
		// 写入请求头
		setRequestHeader(request, header);
		// 写入配置
		setRequestConfig(request, config);
		// cookie处理
		HttpClientContext context = createHttpClientContext(cookies);
		
		CloseableHttpResponse response = null;
		try {
			// 执行请求接收响应
			if(context == null) {
				response = httpClient.execute(request);
			}else {
				response = httpClient.execute(request, context);
			}
			
			if (response == null) {
				throw new HttpClientIllegalArgumentException("HttpResponse is null.");
			}
			
			// 读取状态码与响应数据
			int statusCode = response.getStatusLine().getStatusCode();
			result.setStatusCode(statusCode);
			String entityText = readResponse(response);
			result.setEntityText(entityText);
		} catch (ClientProtocolException e) {
			String m = "客户端的请求协议不支持";
			throw new HttpClientIllegalArgumentException(m, e);
		} catch (ConnectTimeoutException e) {
			// 连接超时
			String m = "连接超时";
			throw new HttpClientIllegalArgumentException(m, e);
		} catch (InterruptedIOException e) {
			// 指示I/O操作被中断的信号。一个interruptedioexception是抛出一个输入或输出转移已终止，因为线程执行被中断。
			String m = "请求异常中断";
			throw new HttpClientIllegalArgumentException(m, e);
		} catch (UnknownHostException e) {
			// 目标服务器不可达
			String m = "目标服务器不可达";
			throw new HttpClientIllegalArgumentException(m, e);
		} catch (SSLException e) {
			// ssl握手异常
			String m = "ssl握手异常";
			throw new HttpClientIllegalArgumentException(m, e);
		} catch (Exception e) {
			// 【暂用】HttpClient超时为内部运行时异常，目前没有收集完整的，先捕获Exception保证覆盖率
			String m = "HttpClient连接超时";
			throw new HttpClientTimeoutException(m, e);
		} finally {
			IOUtils.closeQuietly(response);
		}
		return result;
	}


	





	@Override
	public HttpClientConfig getConfig() {
		return config;
	}

	// -------------------- 核心逻辑 -------------------- //

	/**
	 * 创建请求的对象，POST/GET
	 * @param method
	 * @return
	 */
	protected HttpRequestBase createHttpRequest(String method) {
		HttpRequestBase request = null;
		if (METHOD_POST.equalsIgnoreCase(method)) {
			request = new HttpPost();
		} else if (METHOD_GET.equalsIgnoreCase(method)) {
			request = new HttpGet();
		} else {
			throw new HttpClientIllegalArgumentException("未知的Http请求方法");
		}
		return request;
	}

	/**
	 * 设置请求的配置，如超时
	 * @param request
	 * @param config
	 */
	protected void setRequestConfig(HttpRequestBase request, HttpClientConfig config) {
		// 如果不传config参数，默认为HttpClient初始化的全局配置
		if (config == null) {
			config = this.config;
		}
		RequestConfig.Builder requestConfigbuilder = RequestConfig.custom();
		// 设置连接超时
		Integer CONNECT_TIMEOUT = config.getConnectTimeout();
		if (CONNECT_TIMEOUT != null) {
			requestConfigbuilder.setConnectionRequestTimeout(CONNECT_TIMEOUT);
			requestConfigbuilder.setConnectTimeout(CONNECT_TIMEOUT);
		}
		// 设置通信超时
		Integer SOCKET_TIMEOUT = config.getSocketTimeout();
		if (SOCKET_TIMEOUT != null) {
			requestConfigbuilder.setSocketTimeout(SOCKET_TIMEOUT);
		}
		RequestConfig requestConfig = requestConfigbuilder.build();
		request.setConfig(requestConfig);
	}

	/**
	 * 写入请求的头，如编码、请求类型等
	 * @param request
	 * @param header
	 */
	protected void setRequestHeader(HttpRequestBase request, Map<String, Object> header) {
		if (header != null) {
			for (Entry<String, Object> entry : header.entrySet()) {
				String key = entry.getKey();
				String value = entry.getValue() == null ? "" : entry.getValue().toString();
				request.setHeader(key, value);
			}
		}
	}

	/**
	 * 写入请求地址与请求体，主要数据
	 * @param request HttpClient请求类型对象
	 * @param url 请求地址
	 * @param method 请求方法
	 * @param enctype 请求体类型
	 * @param body 请求体数据
	 * @param config 配置对象
	 */
	protected void setRequestBody(HttpRequestBase request, String url, String method, String enctype, Map<String, Object> params, HttpClientConfig config) {
		// 【细节】如果不传config参数，默认为HttpClient初始化的全局配置
		if (config == null) {
			config = this.config;
		}
		if (params != null) {
			// 将实体参数转换为HttpEntity
			HttpEntity httpEntity = null;
			// 普通文本格式的数据
			if (ENCTYPE_DEFAULT.equalsIgnoreCase(enctype)) {
				List<NameValuePair> nvps = new ArrayList<NameValuePair>();
				for (Entry<String, Object> entry : params.entrySet()) {
					String key = entry.getKey();
					String value = entry.getValue() == null ? "" : entry.getValue().toString();
					nvps.add(new BasicNameValuePair(key, value));
				}
				try {
					if (config.getCharset() != null) {
						httpEntity = new UrlEncodedFormEntity(nvps, config.getCharset());
					} else {
						httpEntity = new UrlEncodedFormEntity(nvps);
					}
				} catch (UnsupportedEncodingException e) {
					String m = MessageFormat.format("字符集编码不支持：{0}", config.getCharset());
					throw new HttpClientIllegalArgumentException(m, e);
				}
			}

			// POST方法写入HttpEntity
			if (METHOD_POST.equalsIgnoreCase(method)) {
				HttpPost post = (HttpPost) request;
				post.setEntity(httpEntity);
				request = post;
			}
			// GET方法写入URI
			else if (METHOD_GET.equalsIgnoreCase(method)) {
				String urlParams = null;
				try {
					urlParams = EntityUtils.toString(httpEntity);
				} catch (ParseException e) {
					String m = "参数格式非法：将请求体HttpEntity转换成URL字符串时失败";
					throw new HttpClientIllegalArgumentException(m, e);
				} catch (IOException e) {
					String m = "参数格式非法：将请求体HttpEntity转换成URL字符串时失败";
					throw new HttpClientIllegalArgumentException(m, e);
				}
				int ix = url.indexOf("?");
				url = url + (ix < 0 ? "?" : "") + urlParams;
			}
			// 写入请求地址
			request.setURI(URI.create(url));
		}
	}

	/**
	 * 用于保持cookie
	 * @param cookies
	 * @return
	 */
	private static HttpClientContext createHttpClientContext(Cookie[] cookies) {
		if(cookies == null) {
			return null;
		}
		
		HttpClientContext context = new HttpClientContext();
		if(cookies != null) {
			CookieStore cookieStore = new BasicCookieStore();
			for (Cookie cookie : cookies) {
				BasicClientCookie c = new BasicClientCookie(cookie.getName(), cookie.getValue());
				c.setDomain(cookie.getDomain());
				cookieStore.addCookie(c);
			}
			context.setCookieStore(cookieStore);
		}
		return context;
	}
	
	/**
	 * 读取响应对象中实体的内容
	 * @param response
	 * @return
	 * @throws HttpClientTimeoutException
	 */
	protected String readResponse(HttpResponse response) throws HttpClientTimeoutException {
		HttpEntity entity = response.getEntity();
		if (entity == null) {
			return null;
		}
		String rv = null;
		InputStream input = null;
		
		//获取字符编码, 如果http响应头没有, 从参数获取
		String charset = getCharsetFromContentType(response.getFirstHeader("Content-Type"));
		if (charset==null) {
			charset = config.getCharset();
		}
		
		try {
			input = entity.getContent();
			if (charset == null || charset.isEmpty()) {
				rv = IOUtils.toString(input);
			} else {
				rv = IOUtils.toString(input, charset);
			}
		} catch (IllegalStateException e) {
			String m = "从返回的HttpEntity中获取InputStream内容失败";
			throw new HttpClientIllegalArgumentException(m, e);
		} catch (UnsupportedEncodingException e) {
			String m = MessageFormat.format("字符集编码不支持：{0}", charset);
			throw new HttpClientIllegalArgumentException(m, e);
		} catch (IOException e) {
			String m = "HttpClient读取数据超时";
			throw new HttpClientTimeoutException(m, e);
		} finally {
			IOUtils.closeQuietly(input);
		}
		return rv;
	}
	
	/**
	 * 从响应头中获取字符编码
	 * @param contentTypeHeader
	 * @return
	 */
	private String getCharsetFromContentType(Header contentTypeHeader){
		
		if(contentTypeHeader==null)
			return null;
		
		String contentType = contentTypeHeader.getValue();
		if(contentType==null)
			return null;
		
		//text/html; charset=GBK
		String flg = "charset=";
		int idx = contentType.indexOf(flg);
		
		if( idx==-1 )
			return null;
		
		return contentType.substring(idx+flg.length());
	}
	
	// get/set //

	public void setHttpClient(CloseableHttpClient httpClient) {
		this.httpClient = httpClient;
	}

	public void setConfig(HttpClientConfig config) {
		this.config = config;
	}
	
}
